home *** CD-ROM | disk | FTP | other *** search
- /*
- * PostEvent XCMD : A HyperCard XCMD to post a key event. Useful for initiating QuicKeys sequences or
- * Tempo macros. Hasn't been tested with AutoMac or any others that might exist.
- * I use this to run applications with an accompanying QuicKeys sequence from Hypercard.
- * Author: Dewi Williams. Delphi: DEWI. (303) 443 9038.
- * Build notes: To build this, you'll need the latest MacTraps version (with the 2.11 upgrade), as
- * well as the LSC SysEnvirons trap glue and header released by Think on Usenet.
- * You'll also need a LSC version of Apple's MPW header "HyperXCmd.h".
- * Usage: 1. PostEvent modifiers, virtual key. For instance "O",13 would be Option-w.
- * The modifiers are C=command,S=shift, L=CapsLock and O=option.
- * With QuicKeys only, c (lower case) = control -- if you have an extended keyboard.
- * Also, beware of capsLock with QuicKeys - it's accepted, but isn't displayed.
- * You can concatenate the modifiers together: "CO" is command-option.
- * There's a table of virtual key values in Inside Macintosh V, and also in the
- * stack supplied with this XCMD.
- * 2. For QuicKeys only, Clicks or Sequences can be invoked by name instead:
- * PostEvent "Reformat" invokes a sequence called "Reformat".
- * Globals: PostEvent needs to know if Tempo or QuicKeys is running if you use the 2 argument
- * call (the two behave somewhat differently).
- * You can do this by assigning the name ("QuicKeys", "Tempo")
- * to a HyperCard global called "Macro". Do this on an openStack message.
- * Notes: Tempo needs both the charCode and the keyCode to function properly. Obtaining a
- * keyCode from a charCode is problematic, but the inverse can be performed
- * via the KeyTrans trap. This is the reason for the baroque calling parameters.
- */
-
- #include <MacTypes.h>
- #include <MemoryMgr.h>
- #include <OSUtil.h>
- #include <EventMgr.h>
- #include <Environs.h> /* Defines for the SysEnvirons trap */
- #include <ScriptMgr.h> /* Came with the 2.11 upgrade as "xScriptMgr.h" */
- #include <HyperXCmd.h> /* Defines and structures for HyperCard XCMD programming */
- #include "QuicKeys.h" /* Defines and structures for QuicKeys internals access */
-
- /* Defines */
- #define NULL 0L
- #define HiWord(x) (((unsigned short *)&(x))[0])
- #define LoWord(x) (((unsigned short *)&(x))[1])
- #define controlKey 4096
-
- /* Forward references */
- unsigned CalcModifiers(StringPtr str);
- void Post(Handle, int, int, Boolean);
-
- pascal void
- main(paramPtr)
- register XCmdBlockPtr paramPtr;
- {
- Str255 str;
- register unsigned modifiers;
- int theKey;
- Handle hKeyData;
- SysEnvRec env;
- register Boolean isTempo;
- Handle global;
- register int kchrID;
- QuicInitBlock *qb;
- register KeyRecord *kr;
-
- /* Ensure that this is system 4.1 or later. This is needed for KeyTrans and extended keyboard
- * checks. It ensures that KCHR/KMAP keyboard mappings are in place.
- */
- if (SysEnvirons(1, &env) == envNotPresent) {
- /* We beep rather than put up an error message because I don't like hardwiring English
- * strings into programs, and haven't figured out an owned resource type of technique for
- * putting the error messages into resources. Apple needs to set a standard here.
- */
- SysBeep(1);
- return;
- }
-
- /* Ensure that there are the correct number of parameters (either 1 or 2). */
- if (paramPtr->paramCount > 2) {
- SysBeep(1);
- return;
- }
-
- /* Calculate the resource ID of the relevant KCHR resource. The Script Manager has
- * this information. This may be useful if you're using Dvorak mappings, for instance.
- * I haven't really been able to test this - 0 is the US default, and it's the only one I
- * have. I wonder how Tempo works under KanjiTalk, anyway...
- */
- kchrID = (int)GetScript(GetEnvirons(smKeyScript), smScriptKeys);
-
- if ( (hKeyData = GetResource('KCHR', kchrID)) == NULL) {
- /* We've confirmed that it is System 4.1 or later. Strange. */
- SysBeep(1);
- return;
- }
-
- /* If there's only one parameter, it's assumed to be a named QuicKeys sequence or click. */
- if (paramPtr->paramCount == 1) {
- /* Find the QuicKeys data block in the system heap. */
- if ( (qb = FindSysHeap()) == NULL) {
- SysBeep(1);
- return;
- }
-
- /* Retrieve the name. */
- ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
-
- /* Attempt to find the named key record in the program keys, then universal keys. Pascaloids
- * please note - we're relying on short-circuit evaluation here.
- */
- if (((kr = FindKeyRecord(qb, str, TRUE)) != NULL) || ((kr = FindKeyRecord(qb, str, FALSE)) != NULL)) {
- theKey = kr->key >> 8;
- Post(hKeyData, theKey, kr->modifiers, FALSE);
- return;
- }
-
- /* Not found. */
- SysBeep(1);
- } else {
- /* Two parameters. Could be either Tempo or QuicKeys. Checking for QuicKeys in memory
- * isn't sufficient, since they can sort of co-exist (!). So to disambiguate, we check a Hypercard
- * global called "Macro".
- */
-
- /* This version supports only QuicKeys and Tempo. QuicKeys is the default. */
- global = GetGlobal(paramPtr, (StringPtr)"\pMacro");
-
- if ((**global) == 0) { /* Empty string */
- /* There isn't a global called Macro, or it's empty. In this case we have to do a check for
- * the presence of QuicKeys (or toss a coin!).
- */
- isTempo = (FindSysHeap() == NULL) ? TRUE : FALSE;
- } else {
- ZeroToPas(paramPtr, *global, str);
- isTempo = StringEqual(paramPtr, (Str31 *)str, (Str31 *)"\pTempo");
- }
- DisposHandle(global); /* Finished with the global. */
-
- /* First param is the modifiers value. Convert it to a pascal string */
- ZeroToPas(paramPtr,*(paramPtr->params[0]), str);
-
- /* Calculate the modifiers from the string */
- modifiers = CalcModifiers(str);
-
- if (isTempo == FALSE) { /* ! Tempo assumed to be QuicKeys. */
- /* QuicKeys handles Control key sequences on the extended keyboard. Tempo doesn't seem to. */
- if (modifiers&controlKey) {
- /* Make no assumptions about new keyboard models! */
- if (env.keyBoardType <= envStandADBKbd && env.keyBoardType != envAExtendKbd) {
- SysBeep(1);
- return;
- }
- }
- } else {
- /* Tempo 1.2 doesn't handle the control modifier. */
- if (modifiers&controlKey) {
- SysBeep(1);
- return;
- }
- }
-
- /* The second param is the virtual keycode. */
- ZeroToPas(paramPtr,*(paramPtr->params[1]), str);
- theKey = ((int)StrToNum(paramPtr, (Str31 *)str)) & 0x7F;
-
- /* Sanity check: virtual keycodes past 0x60 belong to the extended keyboard (function keys, PageUp
- * etc. To stop any possible confusion, we check for the existence of the extended keyboard before
- * posting any such event.
- */
- if (theKey > 0x60 && env.keyBoardType != envAExtendKbd) {
- SysBeep(1);
- return;
- }
-
- Post(hKeyData, theKey, modifiers, isTempo);
- }
- }
-
- /*
- * This is the code that actually posts the key event(s).
- */
-
- void
- Post(hKeyData, theKey, modifiers, isTempo)
- Handle hKeyData;
- int theKey;
- int modifiers;
- Boolean isTempo;
- {
- register unsigned msg;
- long kResult;
- long state = 0;
- EvQElPtr postEntry;
-
- /* The keyCode parameter passed to the KeyTrans trap consists of the modifier
- * flags in bits 8-15, up/down stroke in bit 7 (1 = up), and the virtual key
- * code in bits 6 - 0. With Tempo, we just need to know what the plain keystroke is,
- * so we pass 0 for modifiers. QuicKeys seems to need both. This is all empirical - I'm not
- * sure why, though I suspect it's something like the difference between a prologue &
- * epilogue trap patch.
- */
-
- modifiers |= btnState; /* The mouse button is UP */
- if (isTempo == FALSE) theKey |= modifiers; /* Already in bits 8-15 */
-
- /* I'm assuming here that KeyTrans doesn't move the heap. Don't see why it should. */
- kResult = KeyTrans(*hKeyData, theKey, &state);
-
- /* kResult consists of 2 16 bit characters to be posted as events (usually the high word of each is 0),
- * high word first. A return value of 0 in either word should not be posted.
- * Tempo needs both the keyCode and the charCode fields to be posted. So we use KeyTrans to get
- * a charCode value from the keyCode, and then add both together and post the result.
- */
- theKey &= 0x7F; /* Cut out any modifiers */
- theKey <<= 8; /* And shift to the keyCode position */
-
- if ( (msg = HiWord(kResult)) != 0) {
- msg |= theKey;
- PPostEvent(keyDown, msg, &postEntry);
-
- /* And tack on the modifiers. */
- postEntry->evtQModifiers = modifiers;
- }
-
- if ( (msg = LoWord(kResult)) != 0) {
- msg |= theKey;
- PPostEvent(keyDown, msg, &postEntry);
-
- /* And tack on the modifiers. */
- postEntry->evtQModifiers = modifiers;
- }
- }
-
- /* Convert the modifier string to its numeric equivalent. This code isn't perfect - it does
- * blind matches without validating for odd characters. For instance "LXO" == "LO".
- */
-
- static unsigned
- CalcModifiers(str)
- register StringPtr str;
- {
- register StringPtr end = str + str[0] + 1;
- register unsigned modifiers = 0;
-
- for (str++;str < end; str++) {
- switch( (*str) ) {
- case 'C':
- modifiers |= cmdKey;
- break;
- case 'S':
- modifiers |= shiftKey;
- break;
- case 'L':
- modifiers |= alphaLock;
- break;
- case 'O':
- modifiers |= optionKey;
- break;
- case 'c':
- modifiers |= controlKey;
- break;
- }
- }
- return modifiers;
- }
-
- /*
- * This is a C version of the example code in Chapter 8 of the QuicKeys manual.
- */
-
- QuicInitBlock *
- FindSysHeap()
- {
- register Ptr endBlk = SysZone->bkLim;
- register QuicInitBlock *qp;
-
- qp = (QuicInitBlock *) &SysZone->heapData;
-
- while(qp != (QuicInitBlock *) endBlk) {
- /* Analyze the block we're looking at. */
- if ((qp->header[0] & 0xC0) == 0x40) { /* Is it non-relocatable? */
- /* Check magic and signature. */
- if (qp->quic.magic == 0xa89f1234 && qp->quic.signature == 'CELN') {
- /* Chapter 8 states that the version number is 1. It's actually 0 (as the example
- * assembly language shows and a quick call to CE Software confirmed).
- */
- if (qp->quic.version == 0) return qp;
- }
- }
-
- /* Time to move on to the next block. The 0xFFFFFF strips off the tag byte(s). */
- qp = (QuicInitBlock *)(((Byte *)qp) + ((* (long *)&qp->header) & 0xFFFFFF));
- }
- return NULL; /* Failed to find it */
- }
-
- /*
- * Find a named key record (Note: only clicks & sequences are named).
- */
-
- KeyRecord *
- FindKeyRecord(qb, name, progKeys)
- QuicInitBlock *qb;
- register StringPtr name;
- Boolean progKeys;
- {
- register KeyRecord *kr = (progKeys == TRUE) ? qb->quic.application : qb->quic.universal;
- register KeyRecord *end = kr + N_QCKEYS;
-
- for (; kr < end; kr++) {
- switch(kr->QKtype) {
- case QK_SEQUENCE:
- if (IUCompString(name, kr->u.QuicSequence.title) == 0) return kr;
- break;
- case QK_CLICK:
- if (IUCompString(name, kr->u.QuicClick.title) == 0) return kr;
- default: /* Doesn't have a name */
- break;
- }
- }
- return NULL;
- }
-